package org.eoti.math;

import org.eoti.util.StringUtil;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Comparator;

/*
 * The MIT License
 *
 * Copyright (c) 2009 Malachi de AElfweald <malachid@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
public class Fraction
implements Comparable<Fraction>, Comparator<Fraction>
{
	private BigInteger numerator, denominator;
	public Fraction()
	{
		setNumerator(null);
		setDenominator(null);
	}
	public Fraction(BigInteger numerator, BigInteger denominator)
	{
		setNumerator(numerator);
		setDenominator(denominator);
	}
	public void setNumerator(BigInteger numerator)
	{
		this.numerator = numerator;
	}
	public void setDenominator(BigInteger denominator)
	{
		this.denominator = denominator;
	}
	public BigInteger getNumerator(){return numerator;}
	public BigInteger getDenominator(){return denominator;}
	public BigDecimal compute(int scale)
	{
		if(numerator == null || denominator == null) return null;
		if(BigInteger.ZERO.equals(numerator) || BigInteger.ZERO.equals(denominator)) return null;
		
		return (new BigDecimal(numerator)).divide(new BigDecimal(denominator), scale, RoundingMode.HALF_UP);
	}
    public BigDecimal compute(MathContext ctx)
    {
        if(numerator == null || denominator == null) return null;
        if(BigInteger.ZERO.equals(numerator) || BigInteger.ZERO.equals(denominator)) return null;

        return (new BigDecimal(numerator)).divide(new BigDecimal(denominator), ctx);
    }

	public BigDecimal inCommon(Fraction other, int scale)
	{
		BigDecimal us = compute(scale);
		BigDecimal them = other.compute(scale);
		if(us == null || them == null) return null;
		String s = StringUtil.longestStartingMatch( us.toPlainString(), them.toPlainString() );
//System.out.format("1: %s\n2: %s\nC: %s\n", us.toPlainString(), them.toPlainString(), s);
		if(s == null || s.length() == 0) return null;
		return new BigDecimal(s);
	}
	
	public String toString()
	{
		return String.format("%s/%s", numerator, denominator);
	}

	/**
	 * Compares this object with the specified object for order.  Returns a negative integer, zero, or a positive integer
	 * as this object is less than, equal to, or greater than the specified object.
	 * <p/>
	 * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) == -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and
	 * <tt>y</tt>.  (This implies that <tt>x.compareTo(y)</tt> must throw an exception iff <tt>y.compareTo(x)</tt> throws
	 * an exception.)
	 * <p/>
	 * <p>The implementor must also ensure that the relation is transitive: <tt>(x.compareTo(y)&gt;0 &amp;&amp;
	 * y.compareTo(z)&gt;0)</tt> implies <tt>x.compareTo(z)&gt;0</tt>.
	 * <p/>
	 * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt> implies that <tt>sgn(x.compareTo(z)) ==
	 * sgn(y.compareTo(z))</tt>, for all <tt>z</tt>.
	 * <p/>
	 * <p>It is strongly recommended, but <i>not</i> strictly required that <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.
	 * Generally speaking, any class that implements the <tt>Comparable</tt> interface and violates this condition should
	 * clearly indicate this fact.  The recommended language is "Note: this class has a natural ordering that is
	 * inconsistent with equals."
	 * <p/>
	 * <p>In the foregoing description, the notation <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
	 * <i>signum</i> function, which is defined to return one of <tt>-1</tt>, <tt>0</tt>, or <tt>1</tt> according to
	 * whether the value of <i>expression</i> is negative, zero or positive.
	 *
	 * @param other the object to be compared.
	 *
	 * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the
	 *         specified object.
	 *
	 * @throws NullPointerException if the specified object is null
	 * @throws ClassCastException   if the specified object's type prevents it from being compared to this object.
	 */
	public int compareTo( Fraction other ){
		if(other == null) throw new NullPointerException("Can't compare to null");
        if(getNumerator().equals(other.getNumerator()) && getDenominator().equals(other.getDenominator()))
            return 0;
        
		Fraction[] fracs = lcd(this, other);
        return fracs[0].getNumerator().compareTo(fracs[1].getNumerator());
	}

	protected static Fraction[] lcd(Fraction a, Fraction b)
	{
        // 1/1 compare to 1/2 should become 2/2 and 1/2
        BigInteger denominator = MathUtil.lcm(a.getDenominator(), b.getDenominator());
        
        BigInteger aNumerator = a.getNumerator().multiply(denominator.divide(a.getDenominator()));
        BigInteger bNumerator = b.getNumerator().multiply(denominator.divide(b.getDenominator()));

//        Fraction c = new Fraction(aNumerator, denominator);
//        Fraction d = new Fraction(bNumerator, denominator);
//
//        System.out.format("lcd(%s, %s)=%s, %s\n", a, b, c, d);
//        return new Fraction[]{c,d};
		return new Fraction[]{
			new Fraction(aNumerator, denominator),
			new Fraction(bNumerator, denominator)
		};
	}

	public Fraction add(Fraction other)
	{
		Fraction[] fracs = lcd(this, other);
		return new Fraction(
			fracs[0].getNumerator().add(fracs[1].getNumerator()),
		  fracs[0].getDenominator()
		);
	}

	public Fraction subtract(Fraction other)
	{
		Fraction[] fracs = lcd(this, other);
		return new Fraction(
			fracs[0].getNumerator().subtract(fracs[1].getNumerator()),
		  fracs[0].getDenominator()
		);
	}

	public Fraction multiply(Fraction other)
	{
		return new Fraction(
			this.getNumerator().multiply(other.getNumerator()),
		  this.getDenominator().multiply(other.getDenominator())
		);
	}

	public Fraction divide(Fraction other)
	{
		return multiply(other.invert());
	}

	public Fraction invert()
	{
		return new Fraction(getDenominator(), getNumerator());
	}

	/**
	 * Compares its two arguments for order.  Returns a negative integer, zero, or a positive integer as the first argument
	 * is less than, equal to, or greater than the second.<p>
	 * <p/>
	 * In the foregoing description, the notation <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
	 * <i>signum</i> function, which is defined to return one of <tt>-1</tt>, <tt>0</tt>, or <tt>1</tt> according to
	 * whether the value of <i>expression</i> is negative, zero or positive.<p>
	 * <p/>
	 * The implementor must ensure that <tt>sgn(compare(x, y)) == -sgn(compare(y, x))</tt> for all <tt>x</tt> and
	 * <tt>y</tt>.  (This implies that <tt>compare(x, y)</tt> must throw an exception if and only if <tt>compare(y, x)</tt>
	 * throws an exception.)<p>
	 * <p/>
	 * The implementor must also ensure that the relation is transitive: <tt>((compare(x, y)&gt;0) &amp;&amp; (compare(y,
	 * z)&gt;0))</tt> implies <tt>compare(x, z)&gt;0</tt>.<p>
	 * <p/>
	 * Finally, the implementor must ensure that <tt>compare(x, y)==0</tt> implies that <tt>sgn(compare(x,
	 * z))==sgn(compare(y, z))</tt> for all <tt>z</tt>.<p>
	 * <p/>
	 * It is generally the case, but <i>not</i> strictly required that <tt>(compare(x, y)==0) == (x.equals(y))</tt>.
	 * Generally speaking, any comparator that violates this condition should clearly indicate this fact.  The recommended
	 * language is "Note: this comparator imposes orderings that are inconsistent with equals."
	 *
	 * @param f1 the first object to be compared.
	 * @param f2 the second object to be compared.
	 *
	 * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater
	 *         than the second.
	 *
	 * @throws NullPointerException if an argument is null and this comparator does not permit null arguments
	 * @throws ClassCastException   if the arguments' types prevent them from being compared by this comparator.
	 */
	public int compare( Fraction f1, Fraction f2 ){
		return f1.compareTo(f2);
	}

	/**
	 * Indicates whether some other object is "equal to" this one.
	 * <p/>
	 * The <code>equals</code> method implements an equivalence relation on non-null object references: <ul> <li>It is
	 * <i>reflexive</i>: for any non-null reference value <code>x</code>, <code>x.equals(x)</code> should return
	 * <code>true</code>. <li>It is <i>symmetric</i>: for any non-null reference values <code>x</code> and <code>y</code>,
	 * <code>x.equals(y)</code> should return <code>true</code> if and only if <code>y.equals(x)</code> returns
	 * <code>true</code>. <li>It is <i>transitive</i>: for any non-null reference values <code>x</code>, <code>y</code>,
	 * and <code>z</code>, if <code>x.equals(y)</code> returns <code>true</code> and <code>y.equals(z)</code> returns
	 * <code>true</code>, then <code>x.equals(z)</code> should return <code>true</code>. <li>It is <i>consistent</i>: for
	 * any non-null reference values <code>x</code> and <code>y</code>, multiple invocations of <tt>x.equals(y)</tt>
	 * consistently return <code>true</code> or consistently return <code>false</code>, provided no information used in
	 * <code>equals</code> comparisons on the objects is modified. <li>For any non-null reference value <code>x</code>,
	 * <code>x.equals(null)</code> should return <code>false</code>. </ul>
	 * <p/>
	 * The <tt>equals</tt> method for class <code>Object</code> implements the most discriminating possible equivalence
	 * relation on objects; that is, for any non-null reference values <code>x</code> and <code>y</code>, this method
	 * returns <code>true</code> if and only if <code>x</code> and <code>y</code> refer to the same object (<code>x ==
	 * y</code> has the value <code>true</code>).
	 * <p/>
	 * Note that it is generally necessary to override the <tt>hashCode</tt> method whenever this method is overridden, so
	 * as to maintain the general contract for the <tt>hashCode</tt> method, which states that equal objects must have
	 * equal hash codes.
	 *
	 * @param obj the reference object with which to compare.
	 *
	 * @return <code>true</code> if this object is the same as the obj argument; <code>false</code> otherwise.
	 *
	 * @see #hashCode()
	 * @see java.util.Hashtable
	 */
	@Override public boolean equals( Object obj ){
		return hashCode() == obj.hashCode();
	}

	/**
	 * Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those
	 * provided by <code>java.util.Hashtable</code>.
	 * <p/>
	 * The general contract of <code>hashCode</code> is: <ul> <li>Whenever it is invoked on the same object more than once
	 * during an execution of a Java application, the <tt>hashCode</tt> method must consistently return the same integer,
	 * provided no information used in <tt>equals</tt> comparisons on the object is modified. This integer need not remain
	 * consistent from one execution of an application to another execution of the same application. <li>If two objects are
	 * equal according to the <tt>equals(Object)</tt> method, then calling the <code>hashCode</code> method on each of the
	 * two objects must produce the same integer result. <li>It is <em>not</em> required that if two objects are unequal
	 * according to the {@link Object#equals(Object)} method, then calling the <tt>hashCode</tt> method on each of the two
	 * objects must produce distinct integer results.  However, the programmer should be aware that producing distinct
	 * integer results for unequal objects may improve the performance of hashtables. </ul>
	 * <p/>
	 * As much as is reasonably practical, the hashCode method defined by class <tt>Object</tt> does return distinct
	 * integers for distinct objects. (This is typically implemented by converting the internal address of the object into
	 * an integer, but this implementation technique is not required by the Java<font size="-2"><sup>TM</sup></font>
	 * programming language.)
	 *
	 * @return a hash code value for this object.
	 *
	 * @see Object#equals(Object)
	 * @see java.util.Hashtable
	 */
	@Override public int hashCode(){
		return toString().hashCode();
	}
}
